Capítulo 3: O Módulo de Teleporte
O ciclo de persistência estava validado. Os testes confirmaram que waypoints sobrevivem entre sessões, separados por jogo e sub-mapa. O arquivo JSON existe no diretório do executor, a estrutura está íntegra, e o DataModule expõe uma API limpa para qualquer código consumidor.
Agora vem a parte que realmente importa para o usuário: mover o personagem de um ponto a outro.
Arquitetura do Módulo
O módulo de teleporte precisa resolver três problemas distintos:
- Teleporte instantâneo — mover o personagem imediatamente
- Teleporte com Tween — mover suavemente ao longo do tempo
- Validação de contexto — garantir que o teleporte faz sentido
Cada problema tem suas próprias armadilhas. Teleporte instantâneo parece trivial até você considerar que o personagem pode não existir no momento da chamada. Tween parece simples até você perceber que o jogador pode morrer no meio do movimento. Validação parece desnecessária até você teleportar para a Third Sea enquanto está na First Sea e o personagem cai no vazio.
local TeleportModule = {} local Players = game:GetService("Players") local TweenService = game:GetService("TweenService") local RunService = game:GetService("RunService") local DataModule = nil -- Será injetado local isTeleporting = false local currentTween = nil local teleportConnection = nil
As variáveis de estado no topo do módulo controlam o comportamento durante teleportes. isTeleporting é um flag crítico — previne que múltiplas chamadas de teleporte se sobreponham. currentTween mantém referência ao Tween ativo para possível cancelamento. teleportConnection guarda eventos que precisam ser limpos.
A variável DataModule será injetada externamente. Isso mantém o módulo de teleporte desacoplado da persistência — pode ser testado com mocks se necessário.
function TeleportModule.SetDataModule(module) DataModule = module end
Funções Auxiliares
Antes das funções principais, algumas utilidades são necessárias:
local function GetCharacter() local player = Players.LocalPlayer if not player then return nil end return player.Character end local function GetHumanoidRootPart() local character = GetCharacter() if not character then return nil end return character:FindFirstChild("HumanoidRootPart") end local function GetHumanoid() local character = GetCharacter() if not character then return nil end return character:FindFirstChildOfClass("Humanoid") end
Estas funções existem porque acessar componentes do personagem é repetitivo e propenso a erros. Centralizar a lógica significa que qualquer ajuste futuro acontece em um único lugar.
O personagem pode não existir em vários momentos: durante respawn, no loading inicial, após morte. Cada função retorna nil nesses casos, permitindo que o código chamador trate apropriadamente.
local function IsCharacterAlive() local humanoid = GetHumanoid() if not humanoid then return false end return humanoid.Health > 0 end
Verificar se o personagem está vivo é essencial para o teleporte Tween. Não faz sentido continuar movendo um corpo morto.
Validação de Sub-Mapa
Esta é a verificação mais importante antes de qualquer teleporte. Um waypoint salvo na Second Sea de Blox Fruits não deve ser acessível da First Sea — o resultado seria imprevisível.
local function ValidateSubMap(waypoint) if not DataModule then return false, "DataModule não configurado" end local currentSubMap = DataModule.GetSubMapId() local waypointSubMap = waypoint.subMapId or "default" if currentSubMap ~= waypointSubMap then return false, string.format( "Waypoint pertence a '%s', você está em '%s'", waypointSubMap, currentSubMap ) end return true, nil end
A função retorna dois valores: um booleano indicando se a validação passou, e uma mensagem de erro se falhou. O padrão de retorno múltiplo é consistente com o resto do sistema.
O fallback para "default" no waypointSubMap trata waypoints criados antes da implementação de sub-mapas — compatibilidade retroativa.
A mensagem de erro é específica e informativa. Em vez de "Teleporte bloqueado", o usuário sabe exatamente qual é o problema: está na First Sea tentando ir para a Third Sea. A UI pode exibir isso diretamente.
Teleporte Instantâneo
O método mais simples. Define o CFrame do HumanoidRootPart diretamente para a posição alvo.
function TeleportModule.TeleportInstant(waypoint) if isTeleporting then return false, "Teleporte em andamento" end -- Validar waypoint if not waypoint then return false, "Waypoint inválido" end if not waypoint.position then return false, "Waypoint sem posição" end -- Validar sub-mapa local subMapValid, subMapError = ValidateSubMap(waypoint) if not subMapValid then return false, subMapError end -- Validar personagem local hrp = GetHumanoidRootPart() if not hrp then return false, "Personagem não encontrado" end if not IsCharacterAlive() then return false, "Personagem morto" end -- Deserializar CFrame local targetCFrame = DataModule.DeserializeCFrame(waypoint.position) if not targetCFrame then return false, "Falha ao deserializar posição" end -- Executar teleporte isTeleporting = true local success = pcall(function() hrp.CFrame = targetCFrame end) isTeleporting = false if not success then return false, "Falha ao definir CFrame" end return true, nil end
A estrutura da função segue um padrão defensivo: validar tudo antes de agir. Cada verificação tem uma mensagem de erro específica. Se algo falhar, o código chamador sabe exatamente o que aconteceu.
O flag isTeleporting é setado mesmo para teleporte instantâneo. Isso previne condições de corrida se a UI chamar teleporte múltiplas vezes rapidamente.
O pcall ao redor da atribuição de CFrame captura erros inesperados. Em teoria, definir CFrame não deveria falhar. Na prática, jogos podem ter sistemas anti-teleporte que detectam mudanças bruscas de posição. O pcall garante que o sistema não quebra se algo der errado.
A função retorna true, nil em sucesso — consistente com o padrão de erro do resto do módulo.
Teleporte com Tween
O método Tween é significativamente mais complexo. O personagem precisa se mover suavemente ao longo do tempo, e várias coisas podem acontecer durante esse período.
function TeleportModule.TeleportTween(waypoint, speed) if isTeleporting then return false, "Teleporte em andamento" end -- Validar waypoint if not waypoint then return false, "Waypoint inválido" end if not waypoint.position then return false, "Waypoint sem posição" end -- Validar sub-mapa local subMapValid, subMapError = ValidateSubMap(waypoint) if not subMapValid then return false, subMapError end -- Validar personagem local hrp = GetHumanoidRootPart() if not hrp then return false, "Personagem não encontrado" end if not IsCharacterAlive() then return false, "Personagem morto" end -- Obter velocidade das configurações se não especificada if not speed and DataModule then local settings = DataModule.GetSettings() speed = settings.tweenSpeed or 50 end speed = speed or 50 -- Validar velocidade if speed < 10 then speed = 10 end if speed > 500 then speed = 500 end -- Deserializar CFrame local targetCFrame = DataModule.DeserializeCFrame(waypoint.position) if not targetCFrame then return false, "Falha ao deserializar posição" end -- Calcular duração baseada na distância e velocidade local startPosition = hrp.Position local targetPosition = targetCFrame.Position local distance = (targetPosition - startPosition).Magnitude local duration = distance / speed -- Mínimo de duração para evitar tweens instantâneos if duration < 0.1 then duration = 0.1 end -- Configurar tween local tweenInfo = TweenInfo.new( duration, Enum.EasingStyle.Linear, Enum.EasingDirection.Out ) isTeleporting = true -- Criar e executar tween currentTween = TweenService:Create(hrp, tweenInfo, { CFrame = targetCFrame }) -- Configurar monitoramento de morte local humanoid = GetHumanoid() if humanoid then teleportConnection = humanoid.Died:Connect(function() TeleportModule.CancelTeleport() end) end -- Executar currentTween:Play() -- Aguardar conclusão currentTween.Completed:Connect(function(playbackState) TeleportModule.CleanupTeleport() if playbackState == Enum.PlaybackState.Completed then -- Teleporte concluído com sucesso end end) return true, nil end
A velocidade pode vir de três fontes: parâmetro da função, configurações do DataModule, ou valor padrão. Essa flexibilidade permite que a UI passe velocidade diretamente ou deixe o módulo buscar das configurações persistidas.
O cálculo de duração é simples: distância dividida por velocidade. Um jogador a 500 studs de distância com velocidade 50 levará 10 segundos para chegar. A duração mínima de 0.1 segundos evita tweens que seriam efetivamente instantâneos.
O TweenInfo usa EasingStyle.Linear por uma razão específica: movimento linear é previsível. Easing curves como Quad ou Sine criam aceleração e desaceleração que podem parecer estranhas em longas distâncias. Linear mantém velocidade constante.
A conexão com humanoid.Died é crítica. Se o jogador morrer durante o teleporte, o tween precisa ser cancelado. Continuar movendo um personagem morto causa comportamento visual estranho e pode interferir com sistemas de respawn.
O evento Completed do tween é conectado para cleanup. O playbackState indica se o tween terminou normalmente ou foi cancelado.
Cancelamento de Teleporte
O teleporte Tween pode precisar ser cancelado em várias situações: morte do personagem, pedido do usuário, início de outro teleporte.
function TeleportModule.CancelTeleport() if not isTeleporting then return false end if currentTween then currentTween:Cancel() currentTween = nil end TeleportModule.CleanupTeleport() return true end function TeleportModule.CleanupTeleport() isTeleporting = false if teleportConnection then teleportConnection:Disconnect() teleportConnection = nil end currentTween = nil end
CancelTeleport para o movimento ativo e limpa o estado. CleanupTeleport é uma função separada porque também é chamada quando o tween completa normalmente — cleanup é necessário em ambos os casos.
A separação dessas funções evita duplicação de código e garante que o estado sempre retorna a um ponto consistente.
Função Unificada de Teleporte
A UI não deveria precisar saber qual método usar. Uma função de alto nível consulta as configurações e escolhe o método apropriado.
function TeleportModule.Teleport(waypoint) if not DataModule then return false, "DataModule não configurado" end local settings = DataModule.GetSettings() local method = settings.teleportMethod or "instant" if method == "tween" then return TeleportModule.TeleportTween(waypoint, settings.tweenSpeed) else return TeleportModule.TeleportInstant(waypoint) end end
Esta função é o ponto de entrada principal que a UI usará. Ela abstrai a complexidade de escolher entre métodos — a UI apenas chama Teleport(waypoint) e o módulo decide como executar.
O fallback para "instant" se o método não for reconhecido é uma decisão de segurança. Melhor teleportar de alguma forma do que não teleportar.
Funções de Estado
A UI precisa consultar o estado do teleporte para feedback visual:
function TeleportModule.IsTeleporting() return isTeleporting end function TeleportModule.GetTeleportProgress() if not isTeleporting or not currentTween then return nil end -- TweenService não expõe progresso diretamente -- Retornar informação disponível return { active = true } end
IsTeleporting é simples — retorna o flag. A UI pode usar isso para desabilitar botões durante teleporte.
GetTeleportProgress é mais limitado. TweenService do Roblox não expõe progresso de tween diretamente. Se a UI precisar de uma barra de progresso, seria necessário implementar tracking manual baseado em tempo decorrido versus duração total. Por enquanto, a função retorna apenas se há teleporte ativo.
Tratamento de Respawn
O personagem pode respawnar durante ou após um teleporte. O módulo precisa reagir apropriadamente:
local respawnConnection = nil function TeleportModule.SetupRespawnHandling() local player = Players.LocalPlayer if not player then return end if respawnConnection then respawnConnection:Disconnect() end respawnConnection = player.CharacterAdded:Connect(function(character) -- Cancelar qualquer teleporte em andamento if isTeleporting then TeleportModule.CancelTeleport() end -- Aguardar personagem carregar completamente local hrp = character:WaitForChild("HumanoidRootPart", 5) if not hrp then warn("[TeleportModule] HumanoidRootPart não encontrado após respawn") end end) end function TeleportModule.CleanupRespawnHandling() if respawnConnection then respawnConnection:Disconnect() respawnConnection = nil end end
CharacterAdded dispara quando um novo personagem é criado — isso acontece no respawn. O handler cancela qualquer teleporte ativo porque o tween estava vinculado ao personagem antigo.
O WaitForChild com timeout garante que o novo personagem está pronto antes de qualquer operação. Sem isso, tentativas imediatas de teleporte após respawn podem falhar.
Validação Completa de Waypoint
Uma função que verifica todas as condições necessárias para teleporte, útil para a UI mostrar preview do estado:
function TeleportModule.CanTeleportTo(waypoint) if isTeleporting then return false, "Teleporte em andamento" end if not waypoint then return false, "Waypoint inválido" end if not waypoint.position then return false, "Waypoint sem posição" end local subMapValid, subMapError = ValidateSubMap(waypoint) if not subMapValid then return false, subMapError end local hrp = GetHumanoidRootPart() if not hrp then return false, "Personagem não disponível" end if not IsCharacterAlive() then return false, "Personagem morto" end return true, nil end
A UI pode chamar CanTeleportTo antes do usuário clicar no botão de teleporte. Se retornar falso, o botão pode ser desabilitado e a mensagem de erro exibida como tooltip.
Isso é melhor UX do que deixar o usuário clicar e depois ver o erro. O feedback é antecipado.
Cálculo de Distância
Informação útil para a UI — mostrar ao usuário quão longe está o waypoint:
function TeleportModule.GetDistanceToWaypoint(waypoint) if not waypoint or not waypoint.position then return nil end local hrp = GetHumanoidRootPart() if not hrp then return nil end local targetCFrame = DataModule.DeserializeCFrame(waypoint.position) if not targetCFrame then return nil end local distance = (targetCFrame.Position - hrp.Position).Magnitude return math.floor(distance) end
A distância é arredondada para baixo. Decimais não agregam informação útil para o usuário — "423 studs" é tão útil quanto "423.7821 studs" e mais legível.
Estimativa de Tempo
Complemento da distância — quanto tempo levará o teleporte Tween:
function TeleportModule.GetEstimatedTravelTime(waypoint, speed) local distance = TeleportModule.GetDistanceToWaypoint(waypoint) if not distance then return nil end if not speed and DataModule then local settings = DataModule.GetSettings() speed = settings.tweenSpeed or 50 end speed = speed or 50 local duration = distance / speed return math.ceil(duration) end
O tempo é arredondado para cima. Melhor o usuário esperar um pouco menos do que o estimado do que mais.
Interface do Módulo
O módulo exporta todas as funções necessárias:
return { -- Configuração SetDataModule = TeleportModule.SetDataModule, SetupRespawnHandling = TeleportModule.SetupRespawnHandling, CleanupRespawnHandling = TeleportModule.CleanupRespawnHandling, -- Teleporte Teleport = TeleportModule.Teleport, TeleportInstant = TeleportModule.TeleportInstant, TeleportTween = TeleportModule.TeleportTween, CancelTeleport = TeleportModule.CancelTeleport, -- Estado IsTeleporting = TeleportModule.IsTeleporting, GetTeleportProgress = TeleportModule.GetTeleportProgress, -- Validação e Info CanTeleportTo = TeleportModule.CanTeleportTo, GetDistanceToWaypoint = TeleportModule.GetDistanceToWaypoint, GetEstimatedTravelTime = TeleportModule.GetEstimatedTravelTime }
A interface é organizada em grupos lógicos: configuração, teleporte, estado, validação. Isso facilita navegação e documentação.
Testes do Módulo
O módulo de teleporte precisa ser validado antes da integração:
local function RunTeleportTests() print("[TeleportModule] Iniciando testes...") -- Teste 1: Estado inicial assert(TeleportModule.IsTeleporting() == false, "Estado inicial incorreto") print("✓ Estado inicial correto") -- Teste 2: Teleporte sem DataModule local success, err = TeleportModule.Teleport({}) assert(success == false, "Deveria falhar sem DataModule") assert(err ~= nil, "Deveria ter mensagem de erro") print("✓ Falha correta sem DataModule") -- Configurar DataModule para próximos testes TeleportModule.SetDataModule(DataModule) -- Teste 3: Teleporte com waypoint inválido success, err = TeleportModule.TeleportInstant(nil) assert(success == false, "Deveria falhar com waypoint nil") print("✓ Validação de waypoint funciona") -- Teste 4: Criar waypoint e teleportar local wp, wpErr = DataModule.AddWaypoint("Teste Teleporte") assert(wp ~= nil, "Falha ao criar waypoint: " .. (wpErr or "")) -- Teste 5: Teleporte instantâneo success, err = TeleportModule.TeleportInstant(wp) if not success then print("⚠ Teleporte instantâneo falhou: " .. (err or "")) else print("✓ Teleporte instantâneo funciona") end -- Teste 6: CanTeleportTo local canTp, canTpErr = TeleportModule.CanTeleportTo(wp) print("✓ CanTeleportTo retorna: " .. tostring(canTp)) -- Teste 7: Distância local distance = TeleportModule.GetDistanceToWaypoint(wp) if distance then print("✓ Distância calculada: " .. distance) else print("⚠ Distância não pôde ser calculada") end -- Cleanup DataModule.RemoveWaypoint(wp.id) DataModule.SaveData() print("[TeleportModule] Testes concluídos!") end
Os testes cobrem os casos principais: estado inicial, falhas esperadas, e operações bem-sucedidas. Alguns testes podem falhar dependendo do estado do personagem — isso é esperado e as mensagens indicam claramente o que aconteceu.
O teste de teleporte instantâneo pode falhar se o personagem não existir ou estiver morto. O teste não falha o assert nesses casos, apenas reporta o ocorrido. Isso é intencional — o teste valida que o módulo se comporta corretamente, não que o teleporte sempre funciona.
Integração dos Módulos
Com DataModule e TeleportModule prontos, a integração é direta:
-- No script principal local DataModule = require(DataModulePath) local TeleportModule = require(TeleportModulePath) -- Configurar dependência TeleportModule.SetDataModule(DataModule) -- Inicializar DataModule.LoadData() TeleportModule.SetupRespawnHandling() -- Uso local waypoints = DataModule.GetWaypoints() if #waypoints > 0 then local result, error = TeleportModule.Teleport(waypoints[1]) if not result then warn("Falha no teleporte: " .. error) end end
O fluxo é limpo: carregar dados, configurar módulos, usar. A injeção de dependência mantém os módulos desacoplados — TeleportModule não precisa saber como DataModule persiste dados, apenas que pode chamar suas funções.
O sistema de teleporte está funcional. As funções TeleportInstant e TeleportTween movem o personagem corretamente. A validação de sub-mapa bloqueia teleportes inválidos com mensagens claras. O flag isTeleporting previne sobreposição de teleportes. A conexão com humanoid.Died cancela tweens se o personagem morrer.
Os testes confirmam que cada componente funciona isoladamente e em conjunto. O módulo está pronto para ser consumido pela interface gráfica — cada botão de teleporte na UI chamará TeleportModule.Teleport(waypoint) e receberá feedback sobre sucesso ou falha.
A próxima etapa é construir essa interface. Os módulos de dados e teleporte são a fundação; a UI é a camada que expõe essa funcionalidade ao usuário de forma visual e interativa. O DataModule fornece waypoints para listar, o TeleportModule executa as ações, e a UI conecta
Comments (0)
No comments yet. Be the first to share your thoughts!